<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- Author: wysaid, blog: wysaid.org, mail: admin@wysaid.org -->
<title>arithmetic</title>
</head>
<script type="text/javascript">

var $ = function(v) { return document.getElementById(v) };
var builtinEquation = [
"x*x+y*y=10",
"pow(sin(x), 2) - y*y=0",
"y = pow(sin(x), 2)",
"x*x-y*y=5",
"y=0.5*x*x-2*x*y+1",
"0.5*pow(x*x+y*y-1, 3) = x*x*y*y*y",
"y =sin(x) * x"
];
</script>
<body onload="$('arithmetic').value=builtinEquation[parseInt(Math.random()*builtinEquation.length) % builtinEquation.length];submit()">

<div style="position:relative;border:groove;background-color: #ddd;margin:20px;width:480px;height:360px;"><div style="margin:10px 0;position:relative;width:100%;height:310px;background-color: #ddd;" id="div" onmousedown="mouseDown(event)" onmouseup="mouseUp(event)" onmousemove="mouseMove(event)">
<canvas id="cvs" style="width:100%;height:100%;position:relative;" width="860" height="650"></canvas></div>
<input style="width:60%;height:20px;margin: 0 2%;border:groove;" type="text" title="a piece of js code" id="arithmetic"><input style="width:100px;height:30px;margin: 0 1%;" type="button" value="submit" onclick="submit()" id="submit"></div>

<script type="text/javascript">
"use strict";

var div = $("div");
var cvs = $("cvs");
var w = div.clientWidth;
var h = div.clientHeight;
cvs.width = w;
cvs.height = h;

var ctx = cvs.getContext("2d");

var L = -7.68, T = -4.96, R = 7.68, B = 4.96;

var stepX = (R - L) / w;
var stepY = (B - T) / h;
var gradient = 4;
var gridStepX = stepX * gradient;
var gridStepY = stepY * gradient;
var tolerance = stepX * stepY;

function submit() {
	var btn = $("submit");
	btn.value="wait...";

	drawArithmeticCurve();
	btn.value="OK";
}

var calcEquation;

function drawArithmeticCurve() {
	
	ctx.fillStyle="#ddd";
	ctx.rect(0, 0, w, h);
	ctx.fill();
	
	var arithmeticRule = $("arithmetic").value;
	arithmeticRule = arithmeticRule.replace("==", "=");

	if(arithmeticRule.indexOf("^") != -1) {
		// arithmeticRule = arithmeticRule.replace(/(\(.*\)(?=^))/gi, "");
		alert("You're using '^', please fix it with 'pow' function!\ne.g. ");
		return;
	}

	var filterRule = ["abs", "acos", "asin", "acos", "PI", "tan", "atan", "atan2", "log", "log2", "log10", "pow", "sign", "sin", "cos", "sqrt", "exp", "E"];

	for(var filter in filterRule) {
		var pattern = "(/(?!Math\.)" + filterRule[filter] + "\\(/gi)";
		var rep = "Math." + filterRule[filter] + "(";
		arithmeticRule = arithmeticRule.replace(eval(pattern), rep);
	}

	// $("arithmetic").value = arithmeticRule; //规范化

	var parts = arithmeticRule.split("=");
	if(parts.length == 2)
	{
		arithmeticRule = "(" + parts[0] + ")" + "-(" + parts[1] + ")";
	}

	var equation = "(function(x, y) { return " + arithmeticRule + ";})";
	
	try {
		calcEquation = eval(equation);  //specify global calculation.
	} catch(e) {
		alert("Your input is not a standard equation with js code.");
	}

	var data = ctx.getImageData(0, 0, w, h);

	drawEquation(data.data, calcEquation);

	ctx.putImageData(data, 0, 0);

	ctx.beginPath();

	ctx.fillStyle="#0a0";
	ctx.strokeStyle = "#d00";
	ctx.lineWidth = 1;

	var i;

	var hh = h / 2.0, hw = w / 2.0;

	for(i = -8; i < 8; ++i) {
		var p1 = (i - L) / stepX;
		var p2 = (i + 1 - L) / stepX;
		ctx.moveTo(p1, hh);
		ctx.lineTo(p2, hh);
		ctx.arc(p1, hh, 2, 0, Math.PI * 2.0);
		ctx.fillText("" + i, p1-5, hh+15);
	}

	for(i = -5; i < 5; ++i) {
		var p1 = (i - T) / stepY;
		var p2 = (i + 1 - T) / stepY;
		ctx.moveTo(hw, p1);
		ctx.lineTo(hw, p2);
		ctx.arc(hw, p1, 2, Math.PI * 0.5, Math.PI * 2.5);
		if(i != 0)
			ctx.fillText("" - i, hw+5, p1+5);
	}

	ctx.fillStyle="#000";
	ctx.fillText("Author URL: http://wysaid.org", 20, 20);
	ctx.stroke();
	ctx.closePath();
}

function putPixel(imageData, px, py)
{
	if(px >= 0 && px < w && py >= 0 && py < h) {
		var index = py * (w * 4) + px * 4;
		imageData[index] = 0;
		imageData[index + 1] = 0;
		imageData[index + 2] = 150;
		imageData[index + 3] = 255;
	}
}

function drawEquation(imageData) {

	for(var x1 = L; x1 < R; x1 += gridStepX) {
		for(var y1 = T; y1 < B; y1 += gridStepY) {

			var weight = getWeight(x1 - stepX, y1 - stepY, x1 + gridStepX + stepX, y1 + gridStepY + stepY, x1 + gridStepX / 2.0, y1 + gridStepY / 2.0, 10, tolerance);

			if(weight > 0) {
				for(var i = 0; i < gradient; ++i) {
					for(var j = 0; j < gradient; ++j) {
						var x2 = x1 + i * stepX;
						var y2 = y1 + j * stepY;

						weight = getWeight(x2, y2, x2 + stepX, y2 + stepY, x2 + stepX / 2.0, y2 + stepY / 2.0, 5, tolerance * 0.1);

						if(weight > 0) {

							var p1x = parseInt((x2 + stepX * 0.5 - L) / stepX);
							var p1y = h - parseInt((y2 + stepY * 0.5 - T) / stepY);
							var p2x = parseInt((x2 - stepX * 0.5 - L) / stepX);
							var p2y = h - parseInt((y2 - stepY * 0.5 - T) / stepY);
							putPixel(imageData, p1x, p1y);
							putPixel(imageData, p1x, p2y);
							putPixel(imageData, p2x, p1y);
							putPixel(imageData, p2x, p2y);
						}

					}
				}
			}

		}
	}


}

function distance(dx, dy) {
    return (Math.sqrt(dx * dx + dy * dy));
}

function getWeight(a, b, c, d, x0, y0, count, toler) {
    var z, f1, f2, norm, delta = 0;
    var dFact = 0.00001;

    for (var j = 0; j < count; j++) {
        z = calcEquation(x0, y0);
        if (isNaN(z))
            break;

        if (Math.abs(z) < toler) {
            var fromCtr = distance((a + c) / 2 - x0, (b + d) / 2 - y0);
            var diag = distance(c - a, b - d);
            var fromEdge = diag / 2 - fromCtr;
            var ctrWt = fromEdge / (diag / 2);
            return ctrWt;
        }

        delta = Math.abs(c - a) * dFact;
        
        f1 = getWeightX(x0, y0, delta);
        
        if (isNaN(f1))
            break;

        delta = Math.abs(d - b) * dFact;
        f2 = getWeightY(x0, y0, delta);
        
        if (isNaN(f2))
            break;
        
        norm = f1 * f1 + f2 * f2;
        if (norm < toler) {
            return 0.0;
        }

        x0 -= f1 * z / norm;
        y0 -= f2 * z / norm;

        if (x0 < a || x0 > c || y0 < b || y0 > d)
            break;
    }

    return 0.0;
}

function getWeightX(xPos, yPos, delta) {
    return (calcEquation(xPos + delta, yPos) - calcEquation(xPos - delta, yPos)) / (2 * delta);
}
function getWeightY(xPos, yPos, delta) {
    return (calcEquation(xPos, yPos + delta) - calcEquation(xPos, yPos - delta)) / (2 * delta);
}

function mouseDown(event) {

}

function mouseUp(event) {

}

function mouseMove(event) {

}

</script>

<div style="width:600px; color:#c00">
	<p>Currently support: "abs", "acos", "asin", "acos", "PI", "tan", "atan", "atan2", "log", "log2", "log10", "pow", "sign", "sin", "cos", "sqrt", "exp", "E"
	</p>
</div>
<p>Source Code: <a href="https://github.com/wysaid/simple-arithmetic" target="_blank">https://github.com/wysaid/simple-arithmetic</a></p>

</body>
</html>